import hashdb_output as output
import hashdb_config_defaults as defaults
import os
import shlex
from collections import namedtuple

DatabaseLocal  = namedtuple('DatabaseLocal', 'target database subtree')
DatabaseRemote = namedtuple('DatabaseRemote', 'database subtree')

def parse_text__mapping(mapping):
    def func(text):
        uppr = text.upper()
        if uppr in mapping:
            return mapping.get(uppr)
        else:
            raise ValueError, 'Invalid value (%s)' % text
    return func

parse_text__verbosity = parse_text__mapping({
    'QUIET'  : output.QUIET,
    'DEFAULT': output.DEFAULT,
    'VERBOSE': output.VERBOSE,
    'DEBUG'  : output.DEBUG
})
parse_text__boolean = parse_text__mapping({
    'TRUE'  : True,
    'YES'   : True,
    'T'     : True,
    'Y'     : True,
    '1'     : True,
    'FALSE' : False,
    'NO'    : False,
    'F'     : False,
    'N'     : False,
    '0'     : False
})

def parse_text__choice(choices):
    return parse_text__mapping(dict((x.uppr(), x) for x in choices))

def parse_text__exists(filename):
    if os.path.exists(filename) and os.path.isfile(filename):
        return filename
    else:
        raise ValueError, '%r is not a valid file'

def parse_text__verbatim(text):
    return text

def parse_text__filename(text):
    if text == '' or text == '""':
        return ''

    s = shlex.shlex(text, posix=True)
    s.whitespace_split = True
    s.whitespace = ''
    s.commenters = ''
    results = list(s)
    if len(results) != 1:
        raise ValueError, 'Invalid filename %r' % text

    return results[0]


def parse_text__filenames(text):
    return shlex.split(text)

CombineDB = namedtuple("CombineDB", "local database remote")
def parse_text__database_combine(text):
    s = shlex.shlex(text, posix=True)
    s.whitespace_split = False
    s.commenters = ''

    parts = list(s)
    if (len(parts) == 5) and (parts[1] == ':' and parts[3] == ':'):
        local, database, remote = parts[0], parts[2], parts[4]
    elif len(parts) == 1:
        local = remote = None
        database = parts[0]
    else:
        raise ValueError, 'Invalid database combine specification %r' % text

    return CombineDB(local, database, remote)

class ConfigSettings(object):
    def __init__(self, settings={}, **kwargs):
        object.__init__(self)

        self._settings = settings.copy()
        self._settings.update(kwargs)

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError, _:
            raise AttributeError, name

    def __setattr__(self, name, value):
        try:
            self[name] = value
        except KeyError, _:
            object.__setattr__(self, name, value)

    def __delattr__(self, name):
        try:
            del self[name]
        except KeyError, _:
            object.__delattr__(self, name)

    def __getitem__(self, name):
        if name in self._settings:
            return self._settings[name]
        if name in defaults.CONFIG_MAP:
            return defaults.CONFIG_MAP[name]
        raise KeyError, name

    def __setitem__(self, name, value):
        if name in defaults.CONFIG_MAP:
            self._settings[name] = value
        super(ConfigSettings, self).__setattr__(name, value)

    def __delitem__(self, name):
        if name in defaults.CONFIG_MAP:
            if name in self._settings:
                del self._settings[name]
        else:
            raise KeyError, name

    def __contains__(self, name):
        return self.is_default(name)

    def is_default(self, name):
        if name not in defaults.CONFIG_MAP:
            raise KeyError, name
        return name not in self._settings

    def keys(self):
        return defaults.CONFIG_MAP.keys()
    def iterkeys(self):
        return defaults.CONFIG_MAP.iterkeys()

    def values(self):
        return list(self.itervalues())
    def itervalues(self):
        for k in self.iterkeys():
            yield self[k]

    def items(self):
        return list(self.iteritems())
    def iteritems(self):
        for k in self.iterkeys():
            yield k, self[k]

    def get(self, name, *args, **kwargs):
        if name not in defaults.CONFIG_MAP:
            raise AttributeError, name

        if (len(args) + len(kwargs) > 1) or ((len(kwargs) == 1) and ('default' not in kwargs)):
            raise AttributeError, 'incorrect arguments'

        if name in self._settings:
            return self._settings[name]

        if len(args) == 1:
            return args[0]
        if len(kwargs) == 1:
            return kwargs['default']

        return defaults.CONFIG_MAP[name]

    def update(self, settings, override=True):
        if isinstance(settings, ConfigSettings):
            settings = settings._settings
        for k,v in settings.iteritems():
            if override or self.is_default(k):
                self[k] = v

    def set(self, name, value, override=True):
        if override or self.is_default(name):
            self[name] = value

    def __str__(self):
        keys = list(self.keys())
        keys.sort()
        padd = max(map(len, keys))

        return 'ConfigSettings(\n' +\
            ''.join('    %-*s = %r,\n' % (padd,k,self[k]) for k in keys) +\
            ')'

    def __repr__(self):
        return 'ConfigSettings(' + ','.join('%*s=%r' % (k,v) for k,v in self.iteritems()) + ')'

def add_single(k):
    def fadd_single(self, value, override=False):
        if override or self.is_default(k):
            self[k] = [value]
        else:
            self[k] = self[k] + [value]
    return fadd_single

def add_multiple(k):
    def fadd_multiple(self, values, override=False):
        if override or self.is_default(k):
            self[k] = list(values)
        else:
            self[k] = self[k] + values
    return fadd_multiple

for k, v in defaults.CONFIG_MAP.iteritems():
    if isinstance(v, list):
        setattr(ConfigSettings, 'add_%s'  % (k if k[-1] != 's' else k[:-1]), add_single(k))
        setattr(ConfigSettings, 'extend_%ss' % (k if k[-1] != 's' else k[:-1]), add_multiple(k))

if __name__ == '__main__':
    items = [x for x in dir(ConfigSettings) if x[0] != '_']
    items.sort()
    print items

